﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
using System.Text.RegularExpressions;
using UnityEngine;
using UnityEngine.Events;

public class UDPModule : MonoBehaviour {

    public string targetIP = "127.0.0.1";
    public int targetPort = 5555;
    public int localPort = -1;
    private static UDPModule _instance;

    private Socket udpSocket;
    private EndPoint endPoint, targetEP;
    private static Regex rgx = new Regex(@"^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$");
    private const int MAX_BUFFER_SIZE = 1024;
    private byte[] buffer = new byte[MAX_BUFFER_SIZE];
    private List<byte[]> bufferList = new List<byte[]>();
    public UnityEvent<byte[]> onReceiveEvent;
    public static UDPModule Instance
    {
        get
        {
            if (_instance == null) throw new Exception("单例为空，禁止调用");
            return _instance;
        }
    }

    /// <summary>
    /// 初始化
    /// </summary>
    private void Awake()
    {
        if (_instance == null)
        {
            _instance = this;
            DontDestroyOnLoad(gameObject);
        }
        else if (Instance != this)
        {
            Destroy(gameObject);
            return;
        }
        udpSocket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
        try
        {
            EndPoint ep = new IPEndPoint(IPAddress.Any, localPort < 0 ? 0 : localPort);
            endPoint = new IPEndPoint(IPAddress.Any, 0);
            if (!VerifyEndPoint(targetIP, targetPort.ToString(), ref targetEP))
                return;
            udpSocket.Bind(ep);
            udpSocket.BeginReceiveFrom(buffer, 0, MAX_BUFFER_SIZE, SocketFlags.None, ref endPoint, ReceiveCb, null);
            Debug.LogFormat("[{0}:{1}]UDP服务器启动成功", ((IPEndPoint)udpSocket.LocalEndPoint).Address, ((IPEndPoint)udpSocket.LocalEndPoint).Port);
        }
        catch (Exception ex)
        {
            Debug.LogError(ex);
            return;
        }
    }

    private void ReceiveCb(IAsyncResult ar)
    {
        if (udpSocket == null) return;
        try
        {
            int len = udpSocket.EndReceiveFrom(ar, ref endPoint);
            if (len <= 0)
                return;
            byte[] temp = new byte[len];
            Array.Copy(buffer, temp, len);
            lock(bufferList)
                bufferList.Add(temp);
            udpSocket.BeginReceiveFrom(buffer, 0, MAX_BUFFER_SIZE, SocketFlags.None, ref endPoint, ReceiveCb, null);
        }
        catch (Exception ex)
        {
            Debug.LogError(ex);
        }
    }

    // Use this for initialization
    void Start () {
		
	}
	
	// Update is called once per frame
	void Update () {
        lock (bufferList)
        {
            while (bufferList.Count > 0)
            {
                if (onReceiveEvent != null)
                    onReceiveEvent.Invoke(bufferList[0]);

                bufferList.RemoveAt(0);
            }
        }
	}
    private void SendTo(byte[] buffer)
    {
        udpSocket.SendTo(buffer, targetEP);
    }
    private void OnDestroy()
    {
        Debug.LogFormat("[{0}:{1}]UDP服务器关闭", ((IPEndPoint)udpSocket.LocalEndPoint).Address, ((IPEndPoint)udpSocket.LocalEndPoint).Port);
        udpSocket.Close();
        udpSocket = null;
    }

    /// <summary>
    /// 验证IP地址和port
    /// </summary>
    /// <param name="ep"></param>
    /// <returns></returns>
    public static bool VerifyEndPoint(string ip, string port, ref EndPoint ep)
    {
        if (!rgx.IsMatch(ip.Trim()))
        {
            Debug.LogError("IP地址格式设置不正确");
            return false;
        }

        int s_port = 0;
        if (!int.TryParse(port, out s_port))
        {
            Debug.LogError("端口格式不正确");
            return false;
        }
 
        if (s_port >= 65536 || s_port <= 1024)
        {
            Debug.LogError("端口请使用1024~65536之间的任意数值");
            return false;
        }
        ep = new IPEndPoint(IPAddress.Parse(ip.Trim()), s_port);
        return true;
    }
}
